package cn.felord.security.autoconfigure;

import cn.felord.security.autoconfigure.context.ClientContextFilter;
import cn.felord.security.autoconfigure.handler.LoginAuthenticationSuccessHandler;
import cn.felord.security.autoconfigure.handler.SimpleAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

/**
 * The type Servlet web security configuration.
 *
 * @author felord.cn
 * @since 2021 /8/6 18:51
 */
@Configuration(
        proxyBeanMethods = false
)
@ConditionalOnWebApplication(
        type = ConditionalOnWebApplication.Type.SERVLET
)
public class ServletWebSecurityConfiguration {
    private List<WebHttpSecurityCustomizer> httpSecurityCustomizers = Collections.emptyList();

    /**
     * i18n配置
     *
     * @return the reloadable resource bundle message source
     */
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("org/springframework/security/messages");
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultLocale(Locale.CHINA);
        return messageSource;
    }

    /**
     * 该过滤器链只处理登录授权相关请求
     *
     * @param httpSecurity       the http security
     * @param tokenGenerator     the token generator
     * @param userDetailsService the user details service
     * @return the security filter chain
     * @throws Exception the exception
     */
    @Bean
    @Order(Integer.MIN_VALUE)
    SecurityFilterChain defaultWebSecurityFilterChain(HttpSecurity httpSecurity, TokenGenerator<?> tokenGenerator, UserDetailsService userDetailsService) throws Exception {
        SimpleAuthenticationEntryPoint authenticationEntryPoint = new SimpleAuthenticationEntryPoint();

        AntPathRequestMatcher[] antPathRequestMatchers = Arrays.stream(LoginAntPathPatterns.values())
                .map(pattern -> new AntPathRequestMatcher(pattern.pattern()))
                .toArray(AntPathRequestMatcher[]::new);
        OrRequestMatcher requestMatcher = new OrRequestMatcher(antPathRequestMatchers);
        httpSecurity.securityMatcher(requestMatcher)
                .csrf(AbstractHttpConfigurer::disable)
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(configurer ->
                        configurer.requestMatchers("/favicon.ico", "/error").permitAll())
                .sessionManagement(configurer ->
                        configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(configurer ->
                        configurer.authenticationEntryPoint(authenticationEntryPoint))

                .formLogin(configurer ->
                        configurer.successHandler(new LoginAuthenticationSuccessHandler(tokenGenerator, userDetailsService))
                                .failureHandler(new AuthenticationEntryPointFailureHandler(authenticationEntryPoint)))
                .addFilterBefore(new ClientContextFilter(), SecurityContextHolderFilter.class);
        this.httpSecurityCustomizers.forEach((httpSecurityCustomizer) -> httpSecurityCustomizer.customize(httpSecurity));
        return httpSecurity.build();
    }

    /**
     * Sets web security customizers.
     *
     * @param httpSecurityCustomizers the http security customizers
     */
    @Autowired(
            required = false
    )
    void setWebSecurityCustomizers(List<WebHttpSecurityCustomizer> httpSecurityCustomizers) {
        this.httpSecurityCustomizers = httpSecurityCustomizers;
    }
}
